﻿// ===========================================================================
//  File:		msa_datapump.cs
//  Summary:	Inbound (Outbound: Not yet implemented) XML messages to and  
//              from Microsoft Dynamics CRM.
// ===========================================================================
//  Copyright (C) Microsoft Corporation.  All rights reserved.
//
//  This source code is intended only as a supplement to Microsoft
//  Development Tools and/or on-line documentation.  See these other
//  materials for detailed information regarding Microsoft code samples.
//
//  THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//  PARTICULAR PURPOSE.
//
// ===========================================================================
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using Microsoft.Crm.Sdk;
using Microsoft.Crm.Sdk.Metadata;
using Microsoft.Crm.Sdk.Query;
using Microsoft.Crm.SdkTypeProxy;
using Microsoft.Crm.SdkTypeProxy.Metadata;
using System.Net;
using System.Xml;
using System.Linq;
using System.Xml.Linq;
using System.Globalization;

namespace MSA.Integration
{
    class inbound 
    {
        private static CrmAuthenticationToken GetCrmAuthenticationToken(String organizationName)
        {
            //AD authentication only.
            CrmAuthenticationToken authToken = new CrmAuthenticationToken();
            authToken.AuthenticationType = AuthenticationType.AD;
            authToken.OrganizationName = organizationName;

            return authToken;
        }

        public static Boolean CrmTransaction (ICrmService crmService, string xmlIn) 
        {
            string sEntity = string.Empty;
            string sSource = string.Empty;
            string sSourceObject = string.Empty;
            string sAttributeType = string.Empty;
            string sAttributeName = string.Empty;
            Guid sCrmGuid = Guid.Empty;

            XDocument xDoc = new XDocument();
            xDoc = XDocument.Parse(xmlIn);
            
            try
            {            
                sSource = xDoc.Root.Attribute("source").Value;
                sEntity = xDoc.Root.Attribute("crm_entity").Value;
                sSourceObject = xDoc.Root.Attribute("source_object").Value;
            }
            catch(NullReferenceException)
            {
                //the XML does not conform to the XSD therefore exit.
                return false;
            }

            var query = from p in xDoc.Root.Elements("record")
                        select p;
            //Get the records from the xml and iterate through them
            //This is the caller mechanism for the entire xml document.
            foreach (var x in query) 
            {
                if (x.Attribute("crm_id").Value == "")
                {
                    CrmCreate(sEntity, x, crmService);
                }
                else
                {
                    CrmUpdate(sEntity, x, crmService);
                }
            }
            xDoc = null;
            query = null;
            return true;
        }

        private static Guid CrmCreate(string crmEntityName, XElement CrmRecord, ICrmService crmService)
        {
            DynamicEntity crmEntity = new DynamicEntity(crmEntityName);
            Guid lookupGuid = Guid.Empty;
            
            foreach (var a in CrmRecord.Elements())
            {
                if (a.Name == "attrib")
                {
                    crmEntity = BuildDynEntity(crmEntity, a.Attribute("crm_name").Value, a.Value, a.Attribute("crm_type").Value);
                }
                else if (a.Name == "lookup")
                {
                    lookupGuid = CrmGuidLookup(a.Attribute("entity_name").Value, a.Attribute("search").Value, a.Attribute("search_key").Value, a, crmService);
                    crmEntity = BuildDynEntity(crmEntity, a.Attribute("crm_name").Value, lookupGuid.ToString(), "LookupProperty", a.Attribute("entity_name").Value);
                    break;
                }
                else if (a.Name == "search")
                {
                    //Placeholder for future implementation
                }
            }
            return crmService.Create(crmEntity);
        }

        private static void CrmUpdate(string crmEntityName, XElement CrmRecord, ICrmService crmService)  
        {
            DynamicEntity crmEntity = new DynamicEntity(crmEntityName);
            Guid lookupGuid = new Guid(CrmRecord.Attribute("crm_id").Value);

            foreach (var a in CrmRecord.Elements())
            {
                if (a.Name == "attrib")
                {
                    crmEntity = BuildDynEntity(crmEntity, a.Attribute("crm_name").Value, a.Value, a.Attribute("crm_type").Value, crmEntity.GetType().ToString());
                }
                else if (a.Name == "lookup")
                {
                    lookupGuid = CrmGuidLookup(a.Attribute("entity_name").Value, a.Attribute("search").Value, a.Attribute("search_key").Value, a, crmService);
                    crmEntity = BuildDynEntity(crmEntity, a.Attribute("crm_name").Value, lookupGuid.ToString(), "LookupProperty", a.Attribute("entity_name").Value);
                }
                else if (a.Name == "search")
                {
                    //Placeholder for future implementation
                }
            }

            crmEntity[crmEntityName + "id"] = new Key(lookupGuid);
            crmService.Update(crmEntity);
        }

        private static void CrmUpsert(string crmEntityName, string lookupAttribute, string lookupValue, XElement lookup, ICrmService crmService)
        {
            throw new NotImplementedException();
        }
        
        //Gets exactly one record back.
        public static DynamicEntity CrmSearch(string crmEntityName, string searchAttribute, string searchKey, ColumnSet cSet, ICrmService crmService)
        {
            QueryByAttribute q = new QueryByAttribute();
            q.EntityName = crmEntityName;
            q.Attributes = new string[] { searchAttribute };
            q.Values = new object[] { searchKey };
            q.ColumnSet = cSet;
            RetrieveMultipleRequest retrieve = new RetrieveMultipleRequest();
            retrieve.Query = q;
            retrieve.ReturnDynamicEntities = true;

            RetrieveMultipleResponse response = (RetrieveMultipleResponse)crmService.Execute(retrieve);

            if (response.BusinessEntityCollection.BusinessEntities.Count == 0)
                throw new Exception("Search yielded 0 results");

            return response.BusinessEntityCollection.BusinessEntities[0] as DynamicEntity;
        }

        public static Guid CrmGuidLookup(string crmEntityName, string lookupAttribute, string lookupValue, XElement lookup, ICrmService crmService)
        {
            Guid returnId = Guid.Empty;

            //Lookup to see if the record already exists
            QueryExpression q = new QueryExpression();
            q.EntityName = crmEntityName;
            ColumnSet cSet = new ColumnSet(new string[] { crmEntityName + "id" });
            q.ColumnSet = cSet;

            ConditionExpression con1 = new ConditionExpression(lookupAttribute, ConditionOperator.Equal, lookupValue);
            FilterExpression filter = new FilterExpression();
            filter.Conditions.Add(con1);

            q.Criteria = filter;
            RetrieveMultipleRequest retrieve = new RetrieveMultipleRequest();

            retrieve.Query = q;
            retrieve.ReturnDynamicEntities = true;

            RetrieveMultipleResponse response = (RetrieveMultipleResponse)crmService.Execute(retrieve);

            if (response.BusinessEntityCollection.BusinessEntities.Count > 0)
            {
                DynamicEntity crmRecord = response.BusinessEntityCollection.BusinessEntities[0] as DynamicEntity;
                Key keyProperty = (Key)crmRecord.Properties[crmEntityName + "id"];
                returnId = keyProperty.Value;

                XElement record = new XElement("record", new XAttribute("crm_id", returnId.ToString()), 
                                                from c in lookup.Elements("attrib")
                                                where (string)c.Attribute("force_update").Value == "true"
                                                select c);

                CrmUpdate(crmEntityName, record, crmService);  
            }
            else //if action states CreateIfAbsent 
            {
                if (lookup.Attribute("action").Value == "CreateIfAbsent")
                {
                    returnId = CrmCreate(lookup.Attribute("entity_name").Value, lookup, crmService);
                }
                else
                {
                    returnId = Guid.Empty;
                }
            }
            
            return returnId;
        }

        private static DynamicEntity BuildDynEntity(DynamicEntity crmEntity, string CRMAttrName, string CRMAttrValue, string CRMDataType)
        {
            return BuildDynEntity(crmEntity, CRMAttrName, CRMAttrValue, CRMDataType, crmEntity.Name);
        }

        private static DynamicEntity BuildDynEntity(DynamicEntity crmEntity, string CRMAttrName, string CRMAttrValue, string CRMDataType, string CrmLookupEntity)
        {
            switch (CRMDataType)
            {
                case "StringProperty":
                    StringProperty stringProperty = new StringProperty();
                    stringProperty.Value = CRMAttrValue;
                    stringProperty.Name = CRMAttrName;
                    crmEntity.Properties.Add(stringProperty);
                    break;
                case "CrmBooleanProperty":
                    CrmBooleanProperty crmBooleanProperty = new CrmBooleanProperty();
                    CrmBoolean crmBoolean = new CrmBoolean();
                    crmBoolean.Value = Convert.ToBoolean(CRMAttrValue);
                    crmBooleanProperty.Value = crmBoolean;
                    crmBooleanProperty.Name = CRMAttrName;
                    crmEntity.Properties.Add(crmBooleanProperty);
                    break;
                case "CrmNumberProperty":
                    CrmNumberProperty crmNumberProperty = new CrmNumberProperty();
                    CrmNumber crmNumber = new CrmNumber();
                    crmNumber.Value = Convert.ToInt32(CRMAttrValue);
                    crmNumberProperty.Value = crmNumber;
                    crmNumberProperty.Name = CRMAttrName;
                    crmEntity.Properties.Add(crmNumberProperty);
                    break;
                case "CrmDateTimeProperty":
                //CrmDateTimeProperty crmDateTimeProperty = new CrmDateTimeProperty();
                //CrmDateTime crmDateTimeValue = new CrmDateTime();
                //crmDateTimeValue.Value = DateTime.ParseExact(CRMAttrValue, "ddd MMM dd HH:mm:ss zzz yyyy", null).ToString();
                //crmDateTimeProperty.Value = crmDateTimeValue;
                //crmDateTimeProperty.Name = CRMAttrName;
                //crmEntity.Properties.Add(crmDateTimeProperty);
                //break;

                /*   //[TPL] Twitter timestamps contain a slightly odd timezone specifier "+0000" that doesn;t work
                   // well with DateTime.ParseExact(). So we parse it with a DateTimeOffset.ParseExact() instead,
                   // but then that produces a string format that CRM doesn't like, so we convert it to a
                   // regular DateTime in local time, then create the CrmDateTime item using the static
                   // CrmDateTime.FromUser() method. At no time do we convert the DateTime to a string.
                   // Everyone is happy!
                   CrmDateTimeProperty crmDateTimeProperty = new CrmDateTimeProperty();
                   CrmDateTime crmDateTimeValue = new CrmDateTime();
                   DateTimeOffset parsedDateTime = DateTimeOffset.ParseExact(CRMAttrValue, "ddd MMM dd HH:mm:ss zzz yyyy", null);
                   DateTime localDateTime = new DateTime(parsedDateTime.Ticks, DateTimeKind.Local);
                   crmDateTimeProperty.Value = CrmDateTime.FromUser(localDateTime);
                   crmDateTimeProperty.Name = CRMAttrName;
                   crmEntity.Properties.Add(crmDateTimeProperty);
                   break;
                   */
                //////////////CrmDateTimeProperty crmDateTimeProperty = new CrmDateTimeProperty();
                //////////////CrmDateTime crmDateTimeValue = new CrmDateTime();
                //////////////DateTime tempDateTimeValue = DateTime.ParseExact(CRMAttrValue, "ddd MMM dd HH:mm:ss zzz yyyy", null);
                //////////////crmDateTimeValue = CrmDateTime.FromUniversal(tempDateTimeValue.ToUniversalTime());
                //////////////crmDateTimeProperty.Value = crmDateTimeValue;
                //////////////crmDateTimeProperty.Name = CRMAttrName;
                //////////////crmEntity.Properties.Add(crmDateTimeProperty);
                //////////////break;

                    CrmDateTimeProperty crmDateTimeProperty = new CrmDateTimeProperty();
                    CrmDateTime crmDateTimeValue = new CrmDateTime();
                    DateTime tempDateTime = DateTime.ParseExact(CRMAttrValue, "ddd MMM dd HH:mm:ss zzz yyyy", CultureInfo.InvariantCulture).ToUniversalTime();
                    crmDateTimeValue.Value = tempDateTime.ToString("s");

                    crmDateTimeProperty.Value = crmDateTimeValue;
                    crmDateTimeProperty.Name = CRMAttrName;
                    crmEntity.Properties.Add(crmDateTimeProperty);
                    break;



                case "LookupProperty":
                    LookupProperty lookupProperty = new LookupProperty();
                    Lookup crmLookup = new Lookup();
                    crmLookup.type = CrmLookupEntity;
                    crmLookup.Value = new Guid(CRMAttrValue);
                    lookupProperty.Name = CRMAttrName;
                    lookupProperty.Value = crmLookup;
                    crmEntity.Properties.Add(lookupProperty);
                    break;
                case "CrmMoney":
                    break;
            }
            return crmEntity;
        }
    }
}
